package net.simpleframework.swing;

import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.MouseEvent;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.sql.DataSource;
import javax.swing.BoundedRangeModel;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JScrollBar;
import javax.swing.SortOrder;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;

import net.simpleframework.ado.db.AbstractQueryEntitySet;
import net.simpleframework.ado.db.AbstractQueryEntitySet.ResultSetMetaDataCallback;
import net.simpleframework.ado.db.IQueryExtractor;
import net.simpleframework.ado.db.QueryDialect;
import net.simpleframework.ado.db.ResultSetUtils;
import net.simpleframework.ado.db.SQLValue;
import net.simpleframework.core.ado.db.EOrder;
import net.simpleframework.swing.JActions.ActionCallback;
import net.simpleframework.swing.JTableExColumn.FilterExpression;
import net.simpleframework.swing.JTableExDialogs.ProgressWindow;
import net.simpleframework.util.BeanUtils;
import net.simpleframework.util.LocaleI18n;
import net.simpleframework.util.db.SQLParserUtils;

/**
 * 这是一个开源的软件，请在LGPLv3下合法使用、修改或重新发布。
 * 
 * @author 陈侃(cknet@126.com, 13910090885)
 *         http://code.google.com/p/simpleframework/
 *         http://www.simpleframework.net
 */
public class JQuerySetTable<T> extends JTableEx {
	private static final long serialVersionUID = -8027505210640899885L;

	private AbstractQueryEntitySet<T> originalQuerySet, runningQuerySet;

	private boolean databaseSorter = true;

	public JQuerySetTable() {
	}

	public JQuerySetTable(final AbstractQueryEntitySet<T> querySet, final JTableExColumn[] columns) {
		setColumns(columns);
		setQuerySet(querySet);
	}

	public JQuerySetTable(final AbstractQueryEntitySet<T> querySet) {
		this(querySet, null);
	}

	@Override
	protected void initTable() {
		super.initTable();
		setRowSorter(new QuerySetTableSortController(getModel()));
	}

	@Override
	protected void fireTableHeaderPopupMenu(final MouseEvent e) {
		super.fireTableHeaderPopupMenu(e);
		getEnableDatabaseSorterMenuItem().setSelected(isDatabaseSorter());
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public AbstractQueryEntitySet<T> getQuerySet() {
		if (isFixTable()) {
			return ((JQuerySetTable) dataTbl).getQuerySet();
		}
		return originalQuerySet;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	protected AbstractQueryEntitySet<T> getRunningQuerySet() {
		if (isFixTable()) {
			return ((JQuerySetTable) dataTbl).getRunningQuerySet();
		}
		return runningQuerySet != null ? runningQuerySet : originalQuerySet;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	protected AbstractQueryEntitySet<T> getRunningQuerySet2() {
		if (isFixTable()) {
			return ((JQuerySetTable) dataTbl).getRunningQuerySet2();
		}
		return runningQuerySet;
	}

	private int dataFetchSize = 100;

	public int getDataFetchSize() {
		return dataFetchSize;
	}

	public void setDataFetchSize(final int dataFetchSize) {
		setDataFetchSize(dataFetchSize, false);
	}

	public void setDataFetchSize(final int dataFetchSize, final boolean reload) {
		if (this.dataFetchSize != dataFetchSize) {
			this.dataFetchSize = dataFetchSize;

			createRunningQuerySet(null);
			final AbstractQueryEntitySet<T> qs = getRunningQuerySet();
			qs.reset();
			resetSortOrder();
			qs.setFetchSize(dataFetchSize);
			if (reload) {
				final JScrollBar sb = getScrollBar();
				if (sb != null) {
					sb.setValue(0);
				}
				dataLoading(qs);
			}
		}
	}

	public JTableExColumn[] getQuerySetColumns(final AbstractQueryEntitySet<?> querySet) {
		if (querySet == null) {
			return null;
		}
		return (JTableExColumn[]) querySet.doResultSetMetaData(new ResultSetMetaDataCallback() {
			@Override
			public Object doResultSetMetaData(final ResultSetMetaData metaData) throws SQLException {
				final int size = metaData.getColumnCount();
				final JTableExColumn[] columns = new JTableExColumn[size];
				for (int i = 0; i < size; i++) {
					columns[i] = new JTableExColumn(ResultSetUtils.lookupColumnName(metaData, i + 1)
							.toUpperCase());
					try {
						columns[i].setBeanPropertyType(BeanUtils.forName(metaData
								.getColumnClassName(i + 1)));
					} catch (final ClassNotFoundException e) {
					}
				}
				return columns;
			}
		});
	}

	public void setQuerySet(final AbstractQueryEntitySet<T> querySet) {
		setQuerySet(querySet, false);
	}

	public void setQuerySet(final AbstractQueryEntitySet<T> querySet, final boolean resetColumns) {
		this.originalQuerySet = querySet;
		this.runningQuerySet = null;
		if (querySet == null) {
			return;
		}
		removeAdjustmentListener();
		querySet.setFetchSize(getDataFetchSize());
		if (resetColumns) {
			resetColumnModel();
			((DefaultTableModel) getModel()).setRowCount(0);
		}
		if (tableExColumns == null || resetColumns) {
			setColumns(getQuerySetColumns(querySet));
		}

		dataLoadingBackground(querySet);
	}

	private long dataLoadingTime;

	@SuppressWarnings({ "unchecked", "rawtypes" })
	protected void dataLoading(final AbstractQueryEntitySet<T> querySet) {
		if (isFixTable()) {
			((JQuerySetTable) dataTbl).dataLoading(querySet);
			return;
		}

		if (querySet == null) {
			return;
		}

		dataLoadingTime = 0;
		final long l = System.currentTimeMillis();
		ProgressWindow pw = getProgressWindow();

		removeAdjustmentListener();
		clearAllSelection(this);
		if (!isDatabaseSorter()) {
			resetSortOrder();
		}

		final DefaultTableModel tableModel = (DefaultTableModel) getModel();
		final int fetchSize = getDataFetchSize();
		try {
			if (pw != null) {
				pw.pl.setText(LocaleI18n.getMessage("JQuerySetTable.8"));
				pw.pb.setMaximum(Math.min(fetchSize == 0 ? querySet.getCount() : fetchSize,
						querySet.getCount() - querySet.position() - 1));
				pw.tl.setText((System.currentTimeMillis() - l) + "ms");
			}
			final List<JTableExColumn> columns = getTableExAllColumns();
			T o;
			int i = 0;
			while (true) {
				if (pw != null && pw.abort) {
					break;
				}
				if ((i >= fetchSize && fetchSize != 0) || (o = querySet.next()) == null) {
					if (querySet.getCount() == 0) {
						tableModel.setRowCount(0);
					}
					break;
				}
				if (!doRowCallback(o)) {
					continue;
				} else {
					i++;
				}
				final int pos = querySet.position();
				try {
					final Vector<Object> row = new Vector<Object>();
					for (final JTableExColumn column : columns) {
						row.add(BeanUtils.getProperty(o, column.getColumnName()));
					}
					if (pos == 0) {
						tableModel.setRowCount(0);
					}
					tableModel.addRow(row);
				} catch (final Exception e) {
					logger.error(e);
				}

				if (pw != null) {
					final String tl_1 = LocaleI18n.getMessage("JQuerySetTable.9") + " ( ";
					final String tl_2 = " / " + pw.pb.getMaximum() + " )";
					pw.pl.setText(tl_1 + i + tl_2);
					pw.tl.setText((System.currentTimeMillis() - l) + "ms");
					pw.pb.setValue(i);
				}
			}
		} finally {
			dataLoadingTime = System.currentTimeMillis() - l;
			addAdjustmentListener();
			if (pw != null) {
				pw.dispose();
				pw = null;
			}
		}
	}

	protected boolean doRowCallback(final T bean) {
		return true;
	}

	@SuppressWarnings({ "rawtypes" })
	protected void createRunningQuerySet(final JTableExColumn tableExColumn) {
		if (isFixTable()) {
			((JQuerySetTable) dataTbl).createRunningQuerySet(tableExColumn);
			return;
		}

		final Map<String, SQLValue> sqlm = getSql(tableExColumn);
		if (sqlm == null) {
			runningQuerySet = null;
		} else {
			final AbstractQueryEntitySet<T> qs = getQuerySet();
			runningQuerySet = new AbstractQueryEntitySet<T>(qs.getEntityManager(), sqlm.get("sql")) {
				@Override
				public T mapRow(final ResultSet rs, final int rowNum) throws SQLException {
					return qs.mapRow(rs, rowNum);
				}
			};
			runningQuerySet.setFetchSize(getDataFetchSize());
			runningQuerySet.setQueryDialect(new QueryDialect() {
				@Override
				public SQLValue getCountSQL() {
					return sqlm.get("count");
				}
			});
			runningQuerySet.getListeners().addAll(qs.getListeners());
		}
	}

	private Map<String, SQLValue> getSql(final JTableExColumn tableExColumn) {
		final ArrayList<FilterExpression> filters = new ArrayList<FilterExpression>();
		for (final JTableExColumn column : getTableExAllColumns()) {
			final FilterExpression fe = column.getFilterExpression();
			if (fe != null) {
				filters.add(fe);
			}
		}
		final EOrder order = tableExColumn != null ? tableExColumn.getOrder() : EOrder.normal;
		final AbstractQueryEntitySet<?> qs = getQuerySet();
		if (order == EOrder.normal && filters.size() == 0) {
			qs.reset();
			return null;
		}

		final ArrayList<Object> add = new ArrayList<Object>();
		String condition = "";
		if (filters.size() > 0) {
			int i = 0;
			for (final FilterExpression fe : filters) {
				if (i++ > 0) {
					condition += " and ";
				}
				condition += "(" + fe.toString() + ")";
				if (fe.item1 != null) {
					add.add(fe.item1.toValue());
				}
				if (fe.item2 != null) {
					add.add(fe.item2.toValue());
				}
			}
		}

		final Map<String, SQLValue> sqlm = new HashMap<String, SQLValue>();

		final SQLValue sqlValue = qs.getSqlValue();
		String sql = SQLParserUtils.addCondition(getDataSource(), sqlValue.getSql(), condition);
		if (order != EOrder.normal) {
			sql = SQLParserUtils.addOrderBy(getDataSource(), sql, tableExColumn);
		}
		final ArrayList<Object> p = new ArrayList<Object>(add);
		Object[] values = sqlValue.getValues();
		if (values != null) {
			for (final Object o : values) {
				p.add(o);
			}
		}
		sqlm.put("sql", new SQLValue(sql, p.toArray()));

		final SQLValue countValue = qs.getQueryDialect().getCountSQL();
		if (countValue != null) {
			sql = SQLParserUtils.addCondition(getDataSource(), countValue.getSql(), condition);
			final ArrayList<Object> p2 = new ArrayList<Object>(add);
			values = countValue.getValues();
			if (values != null) {
				for (final Object o : values) {
					p2.add(o);
				}
			}
			sqlm.put("count", new SQLValue(sql, p2.toArray()));
		}
		return sqlm;
	}

	private DataSource getDataSource() {
		return getQuerySet().getEntityManager().getDataSource();
	}

	@Override
	protected void setColumnFreezable(final JTableExColumn[] freezableColumns,
			final boolean freezable) {
		if (isAllFetched()) {
			super.setColumnFreezable(freezableColumns, freezable);
		} else {
			if (freezableColumns == null) {
				return;
			}
			boolean m = false;
			for (final JTableExColumn freezableColumn : freezableColumns) {
				if (freezableColumn.isFreezable() != freezable) {
					m = true;
				}
				freezableColumn.setFreezable(freezable);
			}

			if (m) {
				resetColumnModel();
				final List<JTableExColumn> columns = getTableExAllColumns();
				setColumns(columns.toArray(new JTableExColumn[columns.size()]));

				refreshTableData();
			}
		}
	}

	@Override
	public void refreshTableData() {
		final AbstractQueryEntitySet<T> qs = getRunningQuerySet();
		qs.reset();
		resetSortOrder();
		resetFilterDataVector();
		getScrollBar().setValue(0);
		dataLoading(qs);
	}

	/***************************** AdjustmentListener *****************************/

	private final AdjustmentListener adjustmentListener = new QuerySetAdjustmentListener();

	class QuerySetAdjustmentListener implements AdjustmentListener {
		@Override
		public void adjustmentValueChanged(final AdjustmentEvent e) {
			if (e.getValueIsAdjusting()) {
				return;
			}
			final BoundedRangeModel brm = ((JScrollBar) e.getSource()).getModel();
			final int l = brm.getMaximum() - brm.getExtent();
			if (l <= 0 || e.getValue() < (l - 1)) {
				return;
			}
			if (isAllFetched()) {
				return;
			}
			dataLoadingBackground(getRunningQuerySet());
		}
	}

	private void dataLoadingBackground(final AbstractQueryEntitySet<T> querySet) {
		JActions.doActionCallback(this, new ActionCallback() {
			@Override
			public void doAction() {
				dataLoading(querySet);
			}
		});
	}

	@Override
	protected void configureEnclosingScrollPane() {
		super.configureEnclosingScrollPane();
		if (dataLoadingTime > 0) {
			removeAdjustmentListener();
			addAdjustmentListener();
		}
	}

	private void removeAdjustmentListener() {
		final JScrollBar sb = getScrollBar();
		if (sb != null) {
			final ArrayList<AdjustmentListener> al = new ArrayList<AdjustmentListener>();
			for (final AdjustmentListener l : sb.getAdjustmentListeners()) {
				if (QuerySetAdjustmentListener.class.equals(l.getClass())) {
					al.add(l);
				}
			}
			for (final AdjustmentListener l : al) {
				sb.removeAdjustmentListener(l);
			}
		}
	}

	private void addAdjustmentListener() {
		final JScrollBar sb = getScrollBar();
		if (sb != null) {
			sb.addAdjustmentListener(adjustmentListener);
		}
	}

	/***************************** Sort *****************************/

	private class QuerySetTableSortController extends TableSortControllerEx {
		QuerySetTableSortController(final TableModel tableModel) {
			super(tableModel);
		}

		@Override
		public void setSortKeys(final List<? extends SortKey> sortKeys) {
			if (isAllFetched() || !isDatabaseSorter()) {
				super.setSortKeys(sortKeys);
			} else {
				if (sortKeys == null || sortKeys.size() == 0) {
					setSortKeysPrivate(sortKeys);
					return;
				}
				final SortKey sortKey = sortKeys.get(0);
				final JTableExColumn tableExColumn = getPrivateTableExColumn(convertColumnIndexToView(sortKey
						.getColumn()));
				if (tableExColumn == null) {
					return;
				}
				final SortOrder order = sortKey.getSortOrder();
				if (order.ordinal() == tableExColumn.getOrder().ordinal()) {
					return;
				}

				setColumnOrder(tableExColumn, EOrder.values()[order.ordinal()]);
				setSortKeysPrivate(sortKeys);

				getScrollBar().setValue(0);
				createRunningQuerySet(tableExColumn);
				dataLoading(getRunningQuerySet());
			}
		}
	};

	private boolean isAllFetched() {
		final AbstractQueryEntitySet<?> qs = getRunningQuerySet();
		return qs != null && qs.position() >= qs.getCount();
	}

	public boolean isDatabaseSorter() {
		return databaseSorter;
	}

	public void setDatabaseSorter(final boolean databaseSorter) {
		if (isDatabaseSorter() && !isFixTable() && mTableExColumn != null
				&& mTableExColumn.getOrder() != EOrder.normal) {
			createRunningQuerySet(null);
		}
		this.databaseSorter = databaseSorter;
	}

	/***************************** Column Function *****************************/

	private Object function_func(final String func) {
		final String columnName = mTableExColumn.getColumnName();
		final AbstractQueryEntitySet<?> qs = getRunningQuerySet();
		final SQLValue rSQLValue = qs.getSqlValue();
		String sql;
		if (Date.class.isAssignableFrom(mTableExColumn.getBeanPropertyType())) {
			sql = SQLParserUtils.replaceColumn(getDataSource(), rSQLValue.getSql(), columnName);
			final JTableExColumn column = new JTableExColumn(mTableExColumn.getColumnName());
			column.setOrder("max".equals(func) ? EOrder.desc : EOrder.asc);
			sql = SQLParserUtils.addOrderBy(getDataSource(), sql, column);
		} else {
			sql = SQLParserUtils.replaceColumn(getDataSource(), rSQLValue.getSql(), func + "("
					+ columnName + ")");
		}
		Object v = qs.getEntityManager().executeQuery(new SQLValue(sql, rSQLValue.getValues()),
				new IQueryExtractor<Object>() {
					@Override
					public Object extractData(final ResultSet rs) throws SQLException {
						return rs.next() ? rs.getObject(1) : 0;
					}
				});
		if (v instanceof Date) {
			v = convertToString(mTableExColumn, v);
		}
		return v;
	}

	@Override
	protected Object function_sum() {
		return function_func("sum");
	}

	@Override
	protected Object function_avg() {
		return function_func("avg");
	}

	@Override
	protected Object function_max() {
		return function_func("max");
	}

	@Override
	protected Object function_min() {
		return function_func("min");
	}

	/***************************** Actions and PopupMenu *****************************/

	private JCheckBoxMenuItem defaultEnableDatabaseSorterMenuItem;

	protected JCheckBoxMenuItem getEnableDatabaseSorterMenuItem() {
		if (defaultEnableDatabaseSorterMenuItem == null) {
			defaultEnableDatabaseSorterMenuItem = new JCheckBoxMenuItem(JActions.getAction(this,
					"#(JQuerySetTable.5)", null, new ActionCallback() {
						@Override
						public void doAction() {
							setDatabaseSorter(!isDatabaseSorter());
						}
					}));
		}
		return defaultEnableDatabaseSorterMenuItem;
	}

	@Override
	protected JPopupMenu getPopupMenu() {
		if (isFixTable()) {
			return ((JTableEx) dataTbl).getPopupMenu();
		}
		final JPopupMenu popupMenu = super.getPopupMenu();
		popupMenu.addSeparator();
		final JMenu fetchMenu = new JMenu(LocaleI18n.getMessage("JQuerySetTable.6"));
		popupMenu.add(fetchMenu);
		final JMenuItem item1 = new JMenuItem();
		item1.setAction(actions.getFetchSizeAction());
		item1.setText("100");
		fetchMenu.add(item1);
		final JMenuItem item2 = new JMenuItem();
		item2.setAction(actions.getFetchSizeAction());
		item2.setText("200");
		fetchMenu.add(item2);
		final JMenuItem item3 = new JMenuItem();
		item3.setAction(actions.getFetchSizeAction());
		item3.setText("500");
		fetchMenu.add(item3);
		final JMenuItem item4 = new JMenuItem();
		item4.setAction(actions.getFetchSizeAction());
		item4.setText("1000");
		fetchMenu.add(item4);
		final JMenuItem item5 = new JMenuItem();
		item5.setAction(actions.getFetchSizeAction());
		item5.setText("2000");
		fetchMenu.add(item5);
		final JMenuItem item6 = new JMenuItem();
		item6.setAction(actions.getFetchSizeAction());
		item6.setText(LocaleI18n.getMessage("JQuerySetTable.7"));
		fetchMenu.add(item6);
		return popupMenu;
	}

	@Override
	protected JPopupMenu getHeaderMenu() {
		if (mTableExColumn == null) {
			return null;
		}
		final JPopupMenu defaultHeaderMenu = new JPopupMenu();
		defaultHeaderMenu.add(columnMenuItem);
		defaultHeaderMenu.addSeparator();
		defaultHeaderMenu.add(getEnableDatabaseSorterMenuItem());
		defaultHeaderMenu.addSeparator();
		addDefaultHeaderMenu(defaultHeaderMenu);
		addFunctionHeaderMenu(defaultHeaderMenu);
		return defaultHeaderMenu;
	}

	/***************************** Export *****************************/

	@Override
	protected ExportData getCVSExportData() {
		final AbstractQueryEntitySet<T> querySet = getQuerySet();
		final AbstractQueryEntitySet<T> qs = new AbstractQueryEntitySet<T>(
				querySet.getEntityManager(), querySet.getSqlValue()) {
			@Override
			public T mapRow(final ResultSet rs, final int rowNum) throws SQLException {
				return querySet.mapRow(rs, rowNum);
			}
		};
		qs.getListeners().addAll(querySet.getListeners());
		qs.setFetchSize(0);

		return new ExportData() {
			@Override
			public int getCount() {
				return qs.getCount();
			}

			Object object;

			@Override
			public boolean hasMoreElements() {
				return (object = qs.next()) != null;
			}

			@Override
			public Object nextElement() {
				return object;
			}
		};
	}

	/***************************** Others *****************************/

	@Override
	protected void doFilter(final JTableExColumn column, final FilterExpression filterExpression) {
		if (isAllFetched() && getRunningQuerySet2() == null) {
			super.doFilter(column, filterExpression);
		} else {
			if (column == null) {
				return;
			}
			resetSortOrder();
			column.setFilterExpression(filterExpression);
			createRunningQuerySet(column);
			dataLoading(getRunningQuerySet());
		}
	}

	@Override
	protected String getTableStatistics() {
		if (isFixTable()) {
			return ((JTableEx) dataTbl).getTableStatistics();
		}
		final StringBuilder sb = new StringBuilder();
		sb.append("================#(JQuerySetTable.0)================<br>");
		sb.append("#(JQuerySetTable.1) ( ");
		sb.append(JTableExUtils.colorRed(getModel().getRowCount())).append(" )<br>");
		sb.append("#(JQuerySetTable.2) ( ");
		sb.append(JTableExUtils.colorRed(dataLoadingTime)).append(" ) #(JQuerySetTable.3)<br>");
		sb.append("#(JQuerySetTable.4) ( ");
		sb.append(JTableExUtils.colorRed(getRunningQuerySet().getCount())).append(" )<br><br>");
		return sb.toString();
	}

	@Override
	protected JAbstractTableEx createFixTable() {
		return new JQuerySetTable<T>();
	}
}
